/*
 * Copyright 2013 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.keyczar.interop.operations;

import com.google.gson.Gson;

import org.keyczar.Crypter;
import org.keyczar.KeyczarEncryptedReader;
import org.keyczar.KeyczarFileReader;
import org.keyczar.exceptions.Base64DecodingException;
import org.keyczar.exceptions.KeyczarException;
import org.keyczar.interfaces.KeyczarReader;
import org.keyczar.util.Base64Coder;

import java.io.File;
import java.util.Map;

/**
 * Base class for all operations. An operation is a basic 
 * functionality of keyczar such as encrypting or signing 
 * that can be verified. All operations will have a generate
 * function that will generate output that can tested using 
 * the test function.
 */

public abstract class Operation {
  public final String keyPath;
  public final String testData;
  
  public static Operation getOperationByName(String name, String keyPath, String testData)
      throws KeyczarException {
    if (name.equals("unversioned")){
      return new UnversionedSignOperation(keyPath, testData);
    } else if (name.equals("signedSession")){
      return new SignedSessionOperation(keyPath, testData);
    } else if (name.equals("attached")){
      return new AttachedSignOperation(keyPath, testData);
    } else if (name.equals("sign")){
      return new SignOperation(keyPath, testData);
    } else if (name.equals("encrypt")){
      return new EncryptOperation(keyPath, testData);
    } else {
      throw new KeyczarException("Operation does not exist");
    }
  }
  
  /**
   * Sets up the path where keys are stored.
   * @return
   */
  protected Operation(String keyPath, String testData) {
    this.keyPath = keyPath;
    this.testData = testData;
  }
  
  /**
   * Generates output that will later be saved to file and
   * tested using the test function in this and other 
   * implementations of keyczar. An example is the encrypt
   * operation which will return the ciphertext.
   * 
   * @param algorithm: name of key algorithm with size
   * 
   * @param *options: additional parameters listed in children
   * 
   * @raise NotImplementedError: If the child class does not implement
  */
  public abstract byte[] generate(String algorithm, Map<String, String> generateParams)
      throws KeyczarException;

  /**
   * Will return without error if the input is valid.
   * 
   * Will verify that the data generated by generate
   * works correctly. For example in the case of the encrypt
   * operation this will decrypt the ciphertext and check if
   * the value is equal to what was input as plaintext.
   * 
   * @param algorithm: name of key algorithm with size
   * @type algorithm: string
   * 
   * @param chosenParams: list of option names chosen for generate
   * @type chosenParams: list of strings
   * 
   * @param *testOptions: additional parameters listed in children
   * 
   * @raise AssertionError: If the test fails
   * @raise NotImplementedError: If the child class does not implement
   * @param output
   * @param algorithm
   */
  public abstract void test(
      Map<String, String> output, String algorithm,
      Map<String, String> generateParams, Map<String, String> testParams)
          throws KeyczarException;

  /**
   * Gets the path of keys for a specific algorithm
   * @param algorithm
   * @return
   */
  public String getKeyPath(String algorithm) {
    File file1 = new File(keyPath);
    File file2 = new File(file1, algorithm);
    return file2.getPath();
  }

  /**
   * Gets a reader to be used by keyczar for the appropriate parameters
   * @param algorithm
   * @param crypterAlgorithm
   * @param pubKey
   * @return KeyczarReader
   * @throws KeyczarException
   */
  public KeyczarReader getReader(
      String algorithm, String crypterAlgorithm, String pubKey) throws KeyczarException {
    String keysetName = algorithm + crypterAlgorithm;
    if (pubKey != null) {
      keysetName += pubKey;
    }
    KeyczarReader reader = new KeyczarFileReader(getKeyPath(keysetName));
    if (!crypterAlgorithm.equals("")) {
      Crypter crypter = new Crypter(getKeyPath(crypterAlgorithm));
      reader = new KeyczarEncryptedReader(reader, crypter);
    }
    return reader;
  }

  /**
   * Object for gson formatting of output data
   */
  static class Output {
    public final String output;

    public Output(byte[] output) {
      this.output = Base64Coder.encodeWebSafe(output);
    }
  }

  /**
   * Takes a byte array and returns a json formatted string with output
   * @param output
   * @return
   */
  public String formatOutput(byte[] output){
    Gson gson = new Gson();
    return gson.toJson(new Output(output));
  }

  /**
   * Undoes formatting of formatOutput (takes in json string returns bytes)
   * @param output
   * @return
   * @throws Base64DecodingException
   */
  public byte[] readOutput(Map<String, String> output) throws Base64DecodingException{
    return Base64Coder.decodeWebSafe(output.get("output"));
  }

}
